/*
 *  DispatchThreadPool.c
 *  ClientDispatch
 *
 *  Created by Andrey on 13/10/2008.
 *  Copyright 2008 ИУ7-2003. All rights reserved.
 *
 */

#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

#include "ClientQueue.h"
#include "WorkerThreadPool.h"
#include "KAnsiCWrappers.h"
#include "ClientDispatchLog.h"
#include "../Smtp/Smtp.h"

#define CLIENT_DISPATCH_DEBUG

BEGIN_STRUCT_DECLARATION(WorkerThreadPoolEntry)
	int workerThreadId;
	pthread_t workerThread;
	int isOccupied;
END_STRUCT_DECLARATION(WorkerThreadPoolEntry)

WorkerThreadPoolEntry _workerThreadPool[WORKER_THREAD_POOL_CAPACITY];
pthread_mutex_t _workerThreadPoolMutex;

static void PrintCommand(SmtpCommandHeaderPtr commandHeader)
{
	SmtpCommandWithDomainPtr commandWithDomain;
	SmtpCommandWithAddressPtr commandWithAddress;
	SmtpControlCommandPtr controlCommand;
	
	switch (commandHeader->type) {
		case Initiation:
			commandWithDomain = HEADER_TO_COMMAND(commandHeader, SmtpCommandWithDomain);
			Log("{Initiation, %s}", commandWithDomain->domain);
			break;
		case MailTransactionStart:
			commandWithAddress = HEADER_TO_COMMAND(commandHeader, SmtpCommandWithAddress);
			Log("{MailTransactionStart, %s, %s}", commandWithAddress->address.localPart,
				commandWithAddress->address.domain);
			break;
		case RecipientInfo:
			commandWithAddress = HEADER_TO_COMMAND(commandHeader, SmtpCommandWithAddress);
			Log("{RecipientInfo, %s, %s}", commandWithAddress->address.localPart,
				commandWithAddress->address.domain);
			break;
		case Reset:
			controlCommand = HEADER_TO_COMMAND(commandHeader, SmtpControlCommand);
			Log("{Reset}");
		default:
			break;
	}
}

static void* WorkerThreadRoutine(void* arguments)
{
	while (1) 
	{
		CDClientInfoPtr clientInfo = GetFirstClientInQueue();
		if (clientInfo != NULL)
		{
#ifdef CLIENT_DISPATCH_DEBUG		
			Log("Worker thread: first client in queue is %p", clientInfo);
			Log("Worker thread: recently received data is '%s'", clientInfo->recentlyReceived.data);
#endif
			/*
			 Тут-то и происходит вся суть. Данные, которые пришли от клиента находятся в 
			 clientInfo->recentlyReceived.data; берём их и делаем с ними всё, что хотим.
			 После чего ответ клиенту нужно записать в clientInfo->dataToSend.
			*/
			if (SmtpFSMCurrentState(clientInfo->protocolFsm) != ReceivingData)
			{
				PreparseDataFromClient(clientInfo->recentlyReceived);
				SmtpCommandHeaderPtr commandHeader;
				if (clientInfo->recentlyReceived.dataLength > 2)
				{
					commandHeader = ParseDataFromClientManually(clientInfo->recentlyReceived);
					
					Boolean isValidSmtpCommand = (commandHeader != NULL);
					if (isValidSmtpCommand == YES)
					{
						PrintCommand(commandHeader);
						clientInfo->currentCommandHeader = commandHeader;
						SmtpFSMAdvance(clientInfo->protocolFsm, 
									   SmtpCommandTypeToFsmEvent(commandHeader->type), 
									   clientInfo, NULL, 0);
					}
					else
						SendReply(501);
					
					if (commandHeader != NULL)
						free(commandHeader);
				}
				else
					SendReply(501);
			}
			else
			{
				if (clientInfo->recentlyReceived.data[0] != '.')
					SmtpFSMAdvance(clientInfo->protocolFsm, TEXT, clientInfo, NULL, 0);
				else
					SmtpFSMAdvance(clientInfo->protocolFsm, END_TEXT, clientInfo, NULL, 0);
			}
			if (SmtpFSMCurrentState(clientInfo->protocolFsm) != Disconnected)
				ShowEnvelope((SmtpEnvelopePtr)clientInfo->clientData);
			else
			{
				CloseConnection;
				continue;
			}

#ifdef CLIENT_DISPATCH_DEBUG
			Log("Worker thread: new send buffer cloned");
			Log("Worker thread: \tdata at %p containing %s", clientInfo->dataToSend.data, clientInfo->dataToSend.data); 
			Log("Worker thread: \tlength is %d", clientInfo->dataToSend.dataLength);
			Log("Worker thread: \toffset is %d", clientInfo->dataToSend.currentOffset);
#endif
			NTSendData(clientInfo->clientAddress, clientInfo->dataToSend);
		}
		sleep(2);
	}
	
	return NULL;
}

void InitWorkerThreadPool()
{
 	pthread_mutex_init(&_workerThreadPoolMutex, NULL);
	
	pthread_mutex_lock(&_workerThreadPoolMutex);
	for (register int i = 0; i < WORKER_THREAD_POOL_CAPACITY; i++)
	{
		//pthread_cond_init(&(_sleepWakeSignals[i]), NULL);
		
		_workerThreadPool[i].workerThreadId = i;
		pthread_create(&(_workerThreadPool[i].workerThread), 
					   NULL, 
					   WorkerThreadRoutine, 
					   NULL);
		
		_workerThreadPool[i].isOccupied = 0;
	}
	pthread_mutex_unlock(&_workerThreadPoolMutex);
}

void PurgeWorkerThreadPool()
{
	pthread_mutex_lock(&_workerThreadPoolMutex);
	for (register int i = 0; i < WORKER_THREAD_POOL_CAPACITY; i++)
	{
		pthread_cancel(_workerThreadPool[i].workerThread);
	}
	pthread_mutex_unlock(&_workerThreadPoolMutex);
	
	pthread_mutex_destroy(&_workerThreadPoolMutex);
}